package hvzt.minimap.location;

import hvzt.minimap.R;
import hvzt.minimap.activities.ZMapActivity;
import iface.Envelope;
import iface.HvZUser;
import iface.Location;
import iface.MessageInterface;

import java.util.Collection;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

/**
 * This service is responsible for sending GPS coordinates to the server. Coordinates are sent
 * at specified intervals, and the work is done in a separate thread.
 * @author Franklin Pearsall
 *
 */
public class LocationService extends Service {
	
	public static final String TAG = "franklin";
	
	public static final int MSG_REGISTER_CLIENT = 1;
	public static final int MSG_UNREGISTER_CLIENT = 2;
	public static final int MSG_UPDATE_LOCATIONS = 3;
	
	private static final int LOCATION_UPDATE_INTERVAL = 10000;
	private static final int RETRIEVE_LOCATIONS_INTERVAL = 15000;
	
	private static final int NOTIFICATION_ID = 100;
	
	// fields for Messenger and thread
	private boolean m_doRun;
	private Messenger m_client;				// Messenger client from ZMapActivity
	private Messenger m_messenger = new Messenger(new IncomingHandler());
	private boolean m_isCreated = false;
	private int m_startCount;
	private String m_userName;
	
	// fields for location logic
	private LocationManager m_locationManager;
	private boolean m_didStartListening;
	private double m_longitude;
	private double m_latitude;
	
	private NotificationManager m_notificationMananger;

	/**
	 * Initializes member variables.
	 */
	@Override
	public void onCreate() {
		Log.e(TAG, "creating service");
		m_isCreated = true;
		m_doRun = false;
		m_startCount = 0;
		SharedPreferences prefs = getSharedPreferences("hvzt", MODE_PRIVATE);
		m_userName = prefs.getString("netId", "failed");
		m_locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
		m_didStartListening = false;
		m_longitude = 0.0;
		m_latitude = 0.0;
		m_notificationMananger = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
		super.onCreate();
	}
	
	/**
	 * Starts listening for location updates if the service hasn't already started doing so.
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		m_startCount++;
		Log.e(TAG, "start service called, start count: " + m_startCount);
		if (!m_didStartListening) {
			m_didStartListening = true;
			if (m_locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
				Log.e(TAG, "LocationManager: using gps");
				m_locationManager.requestLocationUpdates(
						LocationManager.GPS_PROVIDER, LOCATION_UPDATE_INTERVAL, 0, m_locationListener);
			} else if (m_locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
				Log.e(TAG, "LocationManager: using network");
				m_locationManager.requestLocationUpdates(
						LocationManager.NETWORK_PROVIDER, LOCATION_UPDATE_INTERVAL, 0, m_locationListener);
			} else {
				Log.e(TAG, "LocationManager: ERROR no provider avaialble");
				m_didStartListening = false;
			}
		}
		return super.onStartCommand(intent, flags, startId);
	}

	/**
	 * Returns this service's Messenger's IBinder for the client to bind to.
	 */
	@Override
	public IBinder onBind(Intent arg0) {
		return m_messenger.getBinder();
	}
	
	/**
	 * Stops listening for location updates, stops the thread if its running, and canceles the
	 * persistent notification.
	 */
	@Override
	public void onDestroy() {
		Log.e(TAG, "stopping LocationService...");
		m_locationManager.removeUpdates(m_locationListener);
		if (m_thread.isAlive()) {
			stopThreadRunning();
		}
		m_notificationMananger.cancel(NOTIFICATION_ID);
		super.onDestroy();
	}
	
	/**
	 * Sets run check to false and interrupts thread, effectively stopping it.
	 */
	public void stopThreadRunning() {
		Log.e(TAG, "stop thread");
		m_doRun = false;
		m_thread.interrupt();
	}
	
	/**
	 * Thread holding runnable which sends GPS coordinates, retrieves updated locations from server,
	 * and sends locations to ZMapActivity.
	 * TODO(flp) send actual GPS to server and get location list and write tests for this thread
	 */
	private Thread m_thread = new Thread(new Runnable() {
			@Override
			public void run() {
				while (m_doRun) {
					String user = m_userName;
					Log.e(TAG, "sending location with longitude: " + m_longitude + "; latitude: " + m_latitude);
					Location loc = new Location(user, m_longitude, m_latitude);
					Envelope env = new Envelope(user, loc);
					iface.Status status = MessageInterface.sendEnvelopeToServer(env);
					Log.d(TAG, "result: " + status.toString());
					Collection<HvZUser> collection = status.getLocationList();
					if (collection != null) {
						for (HvZUser h : collection) {
							Log.d(TAG, "user: " + h.toString());
						}
					}
					if (m_client != null) {
						try {
							m_client.send(Message.obtain(null, MSG_UPDATE_LOCATIONS, status));
						} catch (RemoteException e) {
							Log.e(TAG, "failed to send message to client", e);
						}
					}
					try {
						Thread.sleep(RETRIEVE_LOCATIONS_INTERVAL);
					} catch (InterruptedException e) {
						Log.e(TAG, "failed to sleep during location sending and retrieving", e);
					}
				}
			}
	});
	
	/**
	 * Handler for receiving messages from the client.
	 */
	private class IncomingHandler extends Handler {

		@Override
		public void handleMessage(Message msg) {
			switch(msg.what) {
			case MSG_REGISTER_CLIENT:
				Log.e(TAG, "register client please");
				m_client = msg.replyTo;
				break;
			case MSG_UNREGISTER_CLIENT:
				m_client = null;
				break;
			default:
				super.handleMessage(msg);
			}
		}
		
	}
	
	/**
	 * This location listener handles location updates from the Android system.
	 */
	LocationListener m_locationListener = new LocationListener() {

		@Override
		public void onLocationChanged(android.location.Location location) {
			handleLocation(location);
		}

		@Override
		public void onProviderDisabled(String provider) {
			Log.e(TAG, "provider disabled: " + provider);
		}

		@Override
		public void onProviderEnabled(String provider) {
			Log.e(TAG, "provider enabled: " + provider);
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			
		}
	};
	
	/**
	 * Called when the phone's location is changed, updates the GPS coordinates and starts the
	 * thread if it hasn't been starting already.
	 * @param location
	 */
	private void handleLocation(android.location.Location location) {
		m_longitude = location.getLongitude();
		m_latitude = location.getLatitude();
		Log.e(TAG, "new location with longitude: " + m_longitude + "; latitude: " + m_latitude);
		
		// start thread and show notification after first location update
		if (!m_thread.isAlive()) {
			m_doRun = true;
			m_thread.start();
			showNotification();
		}
	}
	
	/**
	 * Create and show notification.
	 */
	private void showNotification() {
		int icon = R.drawable.ic_hvzt;
		CharSequence tickerText = "Sending GPS coordinates";
		Notification notification = new Notification(icon, tickerText, 0);
		// TODO(flp) change icon add intent to open app/zmap when user clicks notification
		Intent startIntent = new Intent(this, ZMapActivity.class);
		startIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
		PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, startIntent, 0);
		notification.flags = Notification.FLAG_ONGOING_EVENT;
		CharSequence contentTitle = "Syncing location data";
		CharSequence contentText = "Select to open HvZT Minimap.";
		notification.setLatestEventInfo(this, contentTitle, contentText, pendingIntent);
		m_notificationMananger.notify(NOTIFICATION_ID, notification);
	}
	
	/**
	 * 
	 * Getters for testing
	 * 
	 */
	
	/**
	 * @return whether or not thread is running
	 */
	public boolean isThreadRunning() {
		return m_thread != null && m_thread.isAlive();
	}
	
	/**
	 * @return whether or not the LocationListener is listening
	 */
	public boolean isListening() {
		return m_didStartListening;
	}
	
	/**
	 * @return whether or not service has been created
	 */
	public boolean isCreated() {
		return m_isCreated;
	}
	
	/**
	 * @return whether or not a current client exists
	 */
	public boolean doesClientExist() {
		return m_client != null;
	}
	
	/**
	 * @return start count
	 */
	public int getStartCount() {
		return m_startCount;
	}
	
}
